/* ************************************************************************
 * @file    event_drv.c
 * @brief   一个简单的Linux字符设备驱动程序，用于模拟输入事件的读写。
 * 该驱动提供了一个接口，允许用户空间应用程序写入（模拟）输入事件，
 * 并从驱动读取这些事件。它支持阻塞读和poll机制。
 *
 * @version 1.1
 * @date    2025年7月6日
 * @author  tiydy, YOUNGQI
 * @company None
 ************************************************************************/

#include <linux/init.h>         // 模块初始化和退出宏定义
#include <linux/module.h>       // 模块相关定义
#include <linux/fs.h>           // 文件系统相关结构体和函数 (file_operations, inode等)
#include <linux/device.h>       // 设备模型相关定义 (class_create, device_create等)
#include <linux/interrupt.h>    // 中断处理相关定义 (此处未使用，但通常用于GPIO中断)
#include <linux/gpio.h>         // GPIO相关定义 (此处未使用，但通常用于GPIO输入)
#include <linux/input.h>        // 输入子系统相关定义 (struct input_event)
#include <linux/wait.h>         // 等待队列相关定义 (DECLARE_WAIT_QUEUE_HEAD, wait_event_interruptible, wake_up_interruptible)
#include <linux/sched.h>        // 进程调度相关定义 (用于等待队列)
#include <linux/poll.h>         // poll机制相关定义 (poll_table, poll_wait)
#include <linux/slab.h>         // 内核内存分配 (kmalloc, kfree) (此处未使用kmalloc/kfree，但cdev_alloc会用到)
#include <linux/fb.h>           // 帧缓冲设备相关定义 (此处未使用)
#include <linux/mm.h>           // 内存管理相关定义 (此处未使用)
#include <asm/uaccess.h>        // 用户空间访问函数 (copy_to_user, copy_from_user)
// #include <asm/device.h>      // 已被 <linux/device.h> 包含，可移除
#include <linux/cdev.h>         // 字符设备相关定义 (cdev_alloc, cdev_init, cdev_add, cdev_del)

#define DEVICENAME "ubuntu_event" ///< 定义设备名称

/**
 * @brief 字符设备结构体指针。
 * 用于管理字符设备的注册和操作。
 */
struct cdev *cdev_t;

/**
 * @brief 设备号，包含主设备号和次设备号。
 * 通过alloc_chrdev_region动态分配。
 */
dev_t dev = 0;

/**
 * @brief 设备类结构体指针。
 * 用于在/sys/class目录下创建设备类，方便udev自动创建设备节点。
 */
static struct class *cdev_class;

/**
 * @brief 声明一个等待队列头。
 * 用于在没有数据可读时，让读取进程进入睡眠状态，直到有数据写入。
 */
static DECLARE_WAIT_QUEUE_HEAD(wq);

/**
 * @brief 存储输入事件数据的结构体。
 * 当用户空间写入事件时，数据存储在此；当用户空间读取事件时，数据从此读取。
 */
struct input_event inputData;

/**
 * @brief 标志位，指示是否有新数据可供读取。
 * 0表示无数据，1表示有数据。
 */
int have_data = 0;

/**
 * @brief 设备打开回调函数。
 *
 * @param inodep inode指针，代表文件节点。
 * @param filep file指针，代表打开的文件实例。
 * @return 成功返回0，失败返回负数错误码。
 */
static int ubuntu_event_open(struct inode *inodep, struct file *filep)
{
    printk(KERN_INFO "event_drv: device opened\n");
    // 清零inputData结构体，确保每次打开时数据是干净的
    memset(&inputData, 0, sizeof(inputData));
    // 重置have_data标志，确保每次打开时没有待处理的数据
    have_data = 0;
    return 0;
}

/**
 * @brief 设备关闭回调函数。
 *
 * @param inodep inode指针。
 * @param filep file指针。
 * @return 成功返回0，失败返回负数错误码。
 */
static int ubuntu_event_release(struct inode *inodep, struct file *filep)
{
    printk(KERN_INFO "event_drv: device closed\n");
    return 0;
}

/**
 * @brief 设备读取回调函数。
 * 从驱动读取一个input_event结构体到用户空间。
 * 如果当前没有数据，进程将阻塞直到有数据写入。
 *
 * @param filep file指针。
 * @param buf 用户空间缓冲区指针，用于存放读取到的数据。
 * @param count 期望读取的字节数。
 * @param offset 文件偏移量指针 (此处未使用)。
 * @return 成功读取的字节数，失败返回负数错误码。
 */
static ssize_t ubuntu_event_read(struct file *filep, char __user *buf, size_t count, loff_t *offset)
{
    // 等待事件发生：如果have_data为0，当前进程将进入睡眠，直到wake_up_interruptible被调用且have_data > 0
    // 如果在等待过程中被信号中断，wait_event_interruptible返回非0值，此时返回-ERESTARTSYS
    if (wait_event_interruptible(wq, have_data > 0))
    {
        return -ERESTARTSYS; // 如果被信号中断，返回
    }

    // 检查用户提供的缓冲区大小是否足够存储一个input_event结构体
    if (count < sizeof(struct input_event))
    {
        return -EINVAL; // 缓冲区太小，无法存储完整事件数据
    }

    // 将内核空间的inputData复制到用户空间的buf
    if (copy_to_user(buf, &inputData, sizeof(struct input_event)))
    {
        return -EFAULT; // 复制失败，通常是用户空间地址无效
    }

    have_data = 0; // 数据已被成功读取，重置have_data标志，表示无新数据
    return sizeof(struct input_event); // 返回实际读取的字节数
}

/**
 * @brief 设备写入回调函数。
 * 从用户空间写入一个input_event结构体到驱动。
 * 写入成功后，会唤醒所有在等待队列wq上睡眠的进程。
 *
 * @param filep file指针。
 * @param buf 用户空间缓冲区指针，包含要写入的数据。
 * @param count 期望写入的字节数。
 * @param offse 文件偏移量指针 (此处未使用)。
 * @return 成功写入的字节数，失败返回负数错误码。
 */
static ssize_t ubuntu_event_write(struct file *filep, const char __user *buf, size_t count, loff_t *offse)
{
    // 检查用户写入的数据大小是否与input_event结构体的大小一致
    if (count != sizeof(struct input_event))
    {
        return -EINVAL; // 写入的数据大小不正确
    }

    // 将用户空间的buf数据复制到内核空间的inputData
    if (copy_from_user(&inputData, buf, sizeof(struct input_event)))
    {
        return -EFAULT; // 复制失败，通常是用户空间地址无效
    }

    have_data = 1; // 标记有新数据可供读取
    wake_up_interruptible(&wq); // 唤醒所有正在等待（read或poll）的进程
    return count; // 返回实际写入的字节数
}

/**
 * @brief 设备poll回调函数。
 * 用于支持select/poll/epoll机制，检查设备是否可读。
 *
 * @param file file指针。
 * @param wait poll_table指针，用于将当前进程添加到等待队列。
 * @return 返回事件掩码，指示设备的状态 (如POLLIN表示可读)。
 */
static unsigned int ubuntu_event_poll(struct file *file, poll_table *wait)
{
    unsigned int mask = 0;

    // 将当前进程添加到等待队列中。
    // 如果没有数据，进程将在此处注册，并在有数据时被唤醒。
    poll_wait(file, &wq, wait);

    // 如果have_data大于0，表示有数据可读
    if (have_data > 0)
    {
        mask |= POLLIN | POLLRDNORM; // 标记为可读事件 (POLLIN) 和普通数据可读 (POLLRDNORM)
    }

    return mask;
}

/**
 * @brief 文件操作结构体。
 * 定义了字符设备支持的各种文件操作函数。
 */
static const struct file_operations fops = {
    .owner = THIS_MODULE,         // 指向拥有此结构的模块，防止模块在被使用时卸载
    .open = ubuntu_event_open,    // 设备打开操作
    .release = ubuntu_event_release, // 设备关闭操作
    .read = ubuntu_event_read,    // 设备读取操作
    .write = ubuntu_event_write,  // 设备写入操作
    .poll = ubuntu_event_poll,    // 设备poll操作
};

/**
 * @brief 模块初始化函数。
 * 当模块加载时调用，负责设备的注册和初始化。
 *
 * @return 成功返回0，失败返回负数错误码。
 */
static int __init ubuntu_event_init(void)
{
    int ret;

    // 1. 动态分配字符设备主/次设备号
    // dev: 输出分配到的设备号
    // 0: 次设备号起始值
    // 1: 分配的设备号数量
    // DEVICENAME: 设备名称，会在/proc/devices中显示
    ret = alloc_chrdev_region(&dev, 0, 1, DEVICENAME);
    if (ret)
    {
        printk(KERN_ERR "event_drv: register fail\n");
        // 如果分配失败，无需注销，因为尚未成功分配
        return ret;
    }

    // 2. 分配并初始化cdev结构体
    cdev_t = cdev_alloc();
    if (!cdev_t)
    {
        printk(KERN_ERR "event_drv: cdev_alloc fail\n");
        unregister_chrdev_region(dev, 1); // 如果cdev分配失败，需要注销已分配的设备号
        return -ENOMEM;
    }

    cdev_t->owner = THIS_MODULE; // 设置cdev的拥有者为当前模块
    cdev_init(cdev_t, &fops);    // 初始化cdev，并关联文件操作结构体fops

    // 3. 将cdev添加到内核，使其与设备号关联
    ret = cdev_add(cdev_t, dev, 1); // 1表示关联一个设备号
    if (ret)
    {
        printk(KERN_ERR "event_drv: cdev_add fail\n");
        // cdev_add失败时，需要释放cdev结构体并注销设备号
        // kobject_put(&cdev_t->kobj); // cdev_del会处理kobject的释放
        cdev_del(cdev_t); // 删除cdev，会释放其内部资源
        unregister_chrdev_region(dev, 1);
        return ret;
    }

    // 4. 创建设备类，用于在/sys/class目录下创建目录
    cdev_class = class_create(THIS_MODULE, DEVICENAME);
    if (IS_ERR(cdev_class)) // 检查class_create是否成功
    {
        printk(KERN_ERR "event_drv: cannot create a cdev_class\n");
        cdev_del(cdev_t);             // 失败时，删除cdev
        unregister_chrdev_region(dev, 1); // 失败时，注销设备号
        return PTR_ERR(cdev_class);   // 返回错误码
    }

    // 5. 创建设备节点，用于在/dev目录下创建设备文件
    // cdev_class: 关联的设备类
    // NULL: 父设备 (无)
    // dev: 设备号
    // NULL: 私有数据 (无)
    // DEVICENAME: 设备文件名 (如/dev/ubuntu_event)
    device_create(cdev_class, NULL, dev, NULL, DEVICENAME);

    printk(KERN_INFO "event_drv: initialized successfully\n");
    return 0;
}

/**
 * @brief 模块退出函数。
 * 当模块卸载时调用，负责设备的注销和资源的释放。
 */
static void __exit ubuntu_event_cleanup(void)
{
    // 1. 销毁设备节点
    device_destroy(cdev_class, dev);
    // 2. 销毁设备类
    class_destroy(cdev_class);
    // 3. 删除cdev结构体
    cdev_del(cdev_t);
    // 4. 注销设备号
    unregister_chrdev_region(dev, 1);
    printk(KERN_INFO "event_drv: cleaned up\n");
}

// 注册模块初始化和退出函数
module_init(ubuntu_event_init);
module_exit(ubuntu_event_cleanup);

// 声明模块许可证为GPL，避免内核污染警告
MODULE_LICENSE("GPL");
